#include <cstring>
#include <cstdlib>
#include "parseXML.h"

ParseXML* ParseXML::getInstance() {
  if ( !instance ) instance = new ParseXML;
  return instance;
}

int ParseXML::getXmlInt(const string& tag) const {
  std::map<string, string>::const_iterator ptr = xmlData.find(tag);
  if ( ptr == xmlData.end() )
    throw string("Didn't find integer tag in xml");
  else return atoi(ptr->second.c_str());
}

string ParseXML::getXmlString(const string& tag) const {
  std::map<string, string>::const_iterator ptr = xmlData.find(tag);
  if ( ptr == xmlData.end() )
    throw string("Didn't find integer tag in xml");
  else return ptr->second;
}

void ParseXML::displayData() const {
  std::map<string, string>::const_iterator ptr = xmlData.begin();
  while ( ptr != xmlData.end() ) {
    cout << ptr->first << ", " << ptr->second << endl;
    ++ptr;
  }
}

void ParseXML::parseXML() {
  parser = XML_ParserCreate(NULL);
  if (!parser) {
    throw std::string("Couldn't allocate memory for parser");
  }

  // The following explanation is taken from:
  // http://www.xml.com/pub/a/1999/09/expat/index.html#setuserdata
  // "In order to be able to pass information between different handlers 
  // without using globals, you'll need to define a data structure to 
  // hold the shared variables. You can then tell expat (with 
  // the XML_SetUserData function) to pass a pointer to this structure 
  // to the handlers. This is typically the first argument received 
  // by most handlers."
  XML_SetUserData(parser, this);
  XML_SetElementHandler(parser, wrapper4Start, wrapper4End);
  XML_SetCharacterDataHandler(parser, wrapper4Chars);
  std::fstream in;
  in.open(filename.c_str(), std::ios::in);
  if (!in) { 
    throw std::string("Cannot open xml file: ")+filename;
  }

  int length = 0;
  in.getline(buff, BUFSIZE);
  while ( true ) {

    if (! XML_Parse(parser, buff, strlen(buff), length)) {
      cout << "Parse error at line "
	         << XML_GetCurrentLineNumber(parser)
	         << XML_ErrorString(XML_GetErrorCode(parser))
           << endl;
      exit(-1);
    }

    if ( in.eof() ) break;
    else in.getline(buff, BUFSIZE);
  }
}

void ParseXML::start(const char *el, const char *attr[]) {
  nextToLastTag = lastTag;
  lastTag = el;
  //cout << el;
  for (int i = 0; attr[i]; i += 2) {
    xmlData.insert(std::make_pair(std::string(attr[i]), attr[i+1]));
  }
}

void ParseXML::stripTrailWhiteSpace(std::string& str) const {
  int length = str.size();   
  int i = length-1;
  while (i >= 0) { 
    if (str[i] != ' ' && str[i] != '\n' && str[i] != '\t') {
      break;
    }
    else if (str[i] == ' ' || str[i] == '\n' || str[i] == '\t') {
      str.erase(i, 1);
    }
    --i;
  }
}

void ParseXML::chars(const char *text, int textlen) {
  // The text is not zero terminated; thus we need the  length:
  std::string str(text, textlen);
  // The text is likely to have trailing white space, e.g. newline, etc
  stripTrailWhiteSpace(str);
  if ( str.size() > 0 ) xmlData[lastTag] = str;
}

void ParseXML::
wrapper4Start(void *data, const char *el, const char **attr) {
  ParseXML * parser = static_cast<ParseXML*>(data);
  parser->start(el, attr);
}

void ParseXML::wrapper4End(void *data, const char *el) {
  ParseXML * parser = static_cast<ParseXML*>(data);
  parser->end(el);
}

void ParseXML::wrapper4Chars(void *data, const char *text, int textlen) {
  ParseXML * parser = static_cast<ParseXML*>(data);
  parser->chars(text, textlen);
}

